#include <stdio.h>
#include <string.h>
#include <mpi.h>
#include "Tasks.h"
#include "Generator.h"
#include "BruteJob.h"
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>

int printdebug=0;

// DANE DO MOJEGO FRAMEWORKA

char pattern[] = 
   	"0123456789"         // ASCII od 48 do 57
   	"ABCDEFGHIJKLMNOPQRSTUVWXYZ" // ASCII od 65 do 90
   	"abcdefghijklmnopqrstuvwxyz"; // ASCII od 97 do 122    	

char pattern_[] = 
   	"0123456789"         // ASCII od 48 do 57
   	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // ASCII od 65 do 90


const int pwdLen = 3;

static int jump=0;

char nazwa_pliku[50];
char polecenie[50];

int PassCheck(char wejsciowy[]){		
	sprintf(polecenie, "./%s %s", nazwa_pliku, wejsciowy);
	
	int ret = system(polecenie);
	
	// char haslo_do_pliku[] = "ari";		
	// int ret = strcmp(wejsciowy, haslo_do_pliku);	
	// printf("Porownywanie %s z %s dalo wynik %d\n", wejsciowy, haslo_do_pliku, ret);
	
	if (ret==0){
		return 1;
	}
	else{
		return 0;
	}
}

void _jobToBuffer(Job *job, char *buffer){
	memcpy(buffer, job, sizeof(Job));
}

void _bufferToJob(char *buffer, Job *job){
	memcpy(job, buffer, sizeof(Job));
}

char jobBuffer[sizeof(Job)];

const int MSG_JOB = 77;
const int MSG_STOP = 78;

void SendTaskToSlave(int rank_i, Tasks *tasks){
	MPI_Request request;
	MPI_Status status;

	if (tasks->AreFinished()==false){
		Job jobForNode = tasks->GetUnusedJobForNode(rank_i);
		_jobToBuffer(&jobForNode, jobBuffer);
		printf("MASTER: Sending job to Slave %d\n", rank_i);
		// tasks->PrintJob(&jobForNode);

		MPI_Isend (jobBuffer, sizeof(jobBuffer), MPI_CHAR,
			       rank_i, MSG_JOB, MPI_COMM_WORLD, &request);
		MPI_Wait (&request, &status);

		// printf("Rank0: MPI_Isend success\n");
		printf("\n");
	}	
}

int startTime=0;
int endTime=0;

void Master(int pool_size, int my_rank){
	MPI_Request request;
	MPI_Status status;
	Tasks* tasks = new Tasks(pattern, pwdLen);
	int rank_i;
	
	printf("Zadania do wykonania: \n");
	tasks->PrintJobs();
	
	startTime = time(NULL);

	printf("Łamie plik: %s\n", nazwa_pliku);
	
	// Najpierw rozeslanie zadan do wszystkich
	for(rank_i=1; rank_i<pool_size; rank_i++){
		if (tasks->AreFinished()==false){
			SendTaskToSlave(rank_i, tasks);
		}
	}

	// Niewolnik wykonal robote, ale jezeli brak rezultatu, to trzeba mu znalezc nowe zajecie ;)
	while(tasks->AreFinished()==false){
		for(rank_i=1; rank_i<pool_size; rank_i++){
			// Sprawdzenie czy Task cos zrobil
			int flag;
			MPI_Iprobe(rank_i, MSG_JOB, MPI_COMM_WORLD, &flag, &status);
			if (flag==1){
				printf("MASTER: Slave %d has a message (flag: %d)\n", rank_i, flag);
				// Odebranie taska
				printf("MASTER: Receiving answer from %d... \n", rank_i);
				MPI_Irecv (jobBuffer, sizeof(jobBuffer), MPI_CHAR, rank_i, MSG_JOB, MPI_COMM_WORLD,
		           	&request);
				MPI_Wait (&request, &status);
				printf("MASTER: Received answer from %d!... \n", rank_i);
			
				// Przerobienie na strukture
				Job result;
				_bufferToJob(jobBuffer, &result);
				tasks->PrintJob(&result);

				// Aktualizacja listy taskow
				tasks->Update(&result);

				// Jezeli znaleziono haslo to koniec
				if (tasks->IsPasswordFound() || tasks->AreFinished())
					break;

				// Znalezienie nowego zadania i przeslania do slavea
				SendTaskToSlave(rank_i, tasks);
			}
		}

		if (tasks->IsPasswordFound() || tasks->AreFinished())
			break;
	}
	endTime = time(NULL);

	// Wyswietlenie podsumowania
	printf("\n\n############## SUMMARY ##############\n");
	printf("Total time in seconds: %d\n", endTime-startTime);
	tasks->PrintJobs();
	
	if (tasks->AreFinished()==1 && tasks->IsPasswordFound()==0) {
		printf("UWAGA!!! UWAGA!!! UWAGA!!! UWAGA!!! UWAGA!!! UWAGA!!! UWAGA!!! \n"
				"MASTER: Hasło nie zostało znalezione\n"
				"Sprawdź czy hasło nie zawiera innych znaków niż %s i zawiera się w przedziale %d znaków\n",
				pattern, pwdLen);
	}

	// Pozostale nody w tym miejscu jeszcze cos robia
	// wiec trzeba odebrac od nich wyniki.
	// Sytuacja tylko jezeli znaleziono haslo
	if (tasks->IsPasswordFound()){
		int captured_flags=0;	
		while(captured_flags<(pool_size-2)){
			int flag;
			for(rank_i=1; rank_i<pool_size; rank_i++){
				MPI_Iprobe(rank_i, MSG_JOB, MPI_COMM_WORLD, &flag, &status);
				if (flag==1) {
					printf("MASTER: Last message from: Slave%d...\n", rank_i);
					MPI_Irecv (jobBuffer, sizeof(jobBuffer), MPI_CHAR, rank_i, MSG_JOB, MPI_COMM_WORLD,
						&request);
					MPI_Wait (&request, &status);
					captured_flags++;
				}
			}
		}
	}
	
	// W tym momencie wszyscy niewolnicy oczekuja na zadanie od Pana. Niestety Pan nie ma juz wiecej zyczen, wiec postanowil zakonczynczyc ich zywot.
	for(rank_i=1; rank_i<pool_size; rank_i++){
		printf("MASTER: Sending MSG_STOP to Slave%d...\n", rank_i);
		MPI_Isend (jobBuffer, sizeof(jobBuffer), MPI_CHAR,
			       rank_i, MSG_STOP, MPI_COMM_WORLD, &request);
		MPI_Wait (&request, &status);
		printf("MASTER: Sent MSG_STOP to Slave%d...\n", rank_i);
	}
	

}

void Slave(int pool_size, int my_rank){
    int my_name_length, count;
    MPI_Request request;
    MPI_Status status;
	Tasks *tasks = new Tasks(pattern, pwdLen);
    for(;;){
		// Otrzymanie zadania
		printf("Slave%d: Starting MPI_Irecv \n", my_rank);
		MPI_Irecv (jobBuffer, sizeof(jobBuffer), MPI_CHAR, 0, MPI_ANY_TAG, MPI_COMM_WORLD,
		           &request);
		MPI_Wait (&request, &status);

		if (status.MPI_TAG==MSG_STOP) {
			printf("Slave%d: Received MSG_STOP. Terminating...\n", my_rank);
			return;
		}

		// printf("MPI_TAG: %d\n", status.MPI_TAG);
		MPI_Get_count (&status, MPI_CHAR, &count);

		// Przerobienie go na strukture
		Job receivedJob;
		_bufferToJob(jobBuffer, &receivedJob);
		printf("Slave%d: Got job\n", my_rank);
		// tasks->PrintJob(&receivedJob);

		// Wykonanie
		BruteJob *bruteJob = new BruteJob(receivedJob, pattern);
		printf("Slave%d: Working...\n", my_rank);
		bruteJob->Run(PassCheck);
		Job result = bruteJob->GetResult();
		printf("Slave%d: JOB DONE!\n", my_rank);
		// tasks->PrintJob(&result);

		// Przerobienie na bufer
		_jobToBuffer(&result, jobBuffer);

		// Odeslanie wyniku
		printf("Slave%d: Sending answer to Master\n", my_rank);
		MPI_Isend (jobBuffer, sizeof(jobBuffer), MPI_CHAR,
				   0, MSG_JOB, MPI_COMM_WORLD, &request);
		MPI_Wait (&request, &status);
		printf("Slave%d: Answer send\n", my_rank);
	}
}



int main(int argc, char *argv[])
{  
  int pool_size, my_rank;

  MPI_Init(&argc, &argv);
  MPI_Comm_size(MPI_COMM_WORLD, &pool_size);
  MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
  
	struct stat sts;
		
	if (stat(argv[1], &sts) == -1)
	{
		printf ("The file %s doesn't exist...\n", argv[1]);
		return -1;
	}
		
	sprintf(nazwa_pliku, "%s", argv[1]);
  
  if (my_rank == 0) {	  		
		Master(pool_size, my_rank);
  } 
  else {
    	Slave(pool_size, my_rank);
  }

  MPI_Finalize();
}
